import { CanActivate, ExecutionContext, Injectable, Inject, UnauthorizedException } from '@nestjs/common';
import { Observable } from 'rxjs';
import { Request, Response } from "express";
import { Reflector } from "@nestjs/core";
import { JwtService } from "@nestjs/jwt";

interface JwtUserData {
  userId: number;
  username: string;
}
// 扩展第三方模块中的类型的数据类型
declare module 'express' {
  interface Request {
    user: JwtUserData
  }
}

@Injectable()
export class AuthGuard implements CanActivate {

  @Inject()
  private reflector: Reflector;

  @Inject(JwtService)
  private jwtService: JwtService;

  /* token校验逻辑 */
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    const request: Request = context.switchToHttp().getRequest();
    const response: Response = context.switchToHttp().getResponse();
    // 1.判断该接口是否需要校验token
    // 需要登录校验的controller前使用 @SetMetadata('require-login', true)，这样就为该controller的元数据对象上添加了一个 require-login:true的属性，然后就可以通过Reflector获取对应controller的元数据对象上的require-login属性
    const requireLogin = this.reflector.getAllAndOverride('require-login', [
      context.getClass(),
      context.getHandler()
    ]);
    if (!requireLogin) return true;
    // 2.判断需要校验token的接口请求头中是否携带了token数据
    const authorization = request.headers.authorization;
    // 请求过程中抛出的异常都会被 ExceptionFilter捕获按照其默认的格式返回（我们也可以自定义异常返回的格式）
    if (!authorization) throw new UnauthorizedException('用户未登录');
    // 3.判断携带了token的接口，token是否正确，是否过期
    try {
      // token一般都是 Bearer
      const token = authorization.split(' ')[1];
      const data = this.jwtService.verify<JwtUserData>(token);
      // 将用户信息提取出来，放到请求体中
      request.user = {
        userId: data.userId,
        username: data.username
      }
      // 实现单token的无限刷新的功能（即：有访问接口就刷新一次token）
      response.header('token', this.jwtService.sign({
        userId: data.userId,
        username: data.username
      }, { expiresIn: '7d' }));
      return true;
    } catch (e) {
      throw new UnauthorizedException('token失效，请重新登录');
    }
  }
}
